﻿using System;
using System.IO;
using System.Net;
using System.Threading;
using Utility;

namespace BlizzardApi.Services
{
    public interface IWebRequestor
    {
        string GetString(string url);
        Stream GetStream(string url);
    }

    public class WebRequestor : IWebRequestor
    {
        private readonly static bool IgnoreExpiresHeader = true;

        private readonly IRequestCache _requestCache;
        private readonly Uri _baseUrl;

        public WebRequestor(string baseUrl, IRequestCache requestCache)
        {
            _requestCache = requestCache;
            _baseUrl = new Uri(baseUrl);
        }

        public string GetString(string url)
        {
            using (var stream = GetStream(url))
                if (stream != null)
                    using (var reader = new StreamReader(stream))
                        return reader.ReadToEnd();
            return null;
        }

        public Stream GetStream(string url)
        {
            Uri uri;
            if (!Uri.TryCreate(url, UriKind.Absolute, out uri) && !Uri.TryCreate(_baseUrl, url, out uri))
                return null;
            return GetStream(uri);            
        }

        private Stream GetStream(Uri uri)
        {
            var cacheResult = _requestCache.Find(uri);
            if (cacheResult != null)
            {
                if (IgnoreExpiresHeader)
                {
                    //ConsoleHelper.WriteLine(ConsoleColor.DarkMagenta, ConsoleColor.White, "Cache Ignored Expired: {0}", uri);
                    return cacheResult.Stream;
                }
                if (cacheResult.Expires.HasValue && cacheResult.Expires.Value > DateTime.UtcNow)
                {
                    ConsoleHelper.WriteLine(ConsoleColor.DarkGreen, ConsoleColor.White, "Cache Not Expired: {0}", uri);
                    return cacheResult.Stream;
                }
            }
            HttpWebRequest request = PrepareRequest(uri, cacheResult);
            HttpWebResponse response;
            try
            {
                response = ExecuteRequest(request);
            }
            catch (WebException ex)
            {
                return HandleException(uri, cacheResult, ex);
            }
            if (cacheResult != null)
                cacheResult.Stream.Dispose();
            ConsoleHelper.WriteLine(ConsoleColor.Red, ConsoleColor.White, "Cache Miss: {0}", uri);
            return HandleResponse(uri, response);
        }

        private static HttpWebRequest PrepareRequest(Uri uri, CacheResult cacheResult)
        {
            var request = (HttpWebRequest) WebRequest.Create(uri);
            if (cacheResult != null)
            {
                if (cacheResult.LastModified.HasValue)
                    request.IfModifiedSince = cacheResult.LastModified.Value;
                if (!string.IsNullOrEmpty(cacheResult.ETag))
                    request.Headers[HttpRequestHeader.IfNoneMatch] = cacheResult.ETag;
            }
            return request;
        }

        private static HttpWebResponse ExecuteRequest(HttpWebRequest request)
        {
            var resetEvent = new ManualResetEvent(false);
            var asyncResult = request.BeginGetResponse(ar => ar.AsyncState.Cast<ManualResetEvent>().Set(), resetEvent);
            ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, TimeoutCallback, request, request.Timeout, true);
            return (HttpWebResponse)request.EndGetResponse(asyncResult);
        }

        private static void TimeoutCallback(object state, bool expired)
        {
            if (expired)
            {
                var request = state as HttpWebRequest;
                if (request != null)
                    request.Abort();
            }
        }

        private Stream HandleException(Uri uri, CacheResult cacheResult, WebException ex)
        {
            var errorResponse = ex.Response as HttpWebResponse;
            if (errorResponse != null)
            {
                if (errorResponse.StatusCode == HttpStatusCode.NotFound)
                    return null;
                if (cacheResult != null && errorResponse.StatusCode == HttpStatusCode.NotModified)
                {
                    _requestCache.Cache(uri, 
                        errorResponse.GetLastModified() ?? cacheResult.LastModified, 
                        errorResponse.GetExpires() ?? cacheResult.Expires, 
                        errorResponse.GetETag() ?? cacheResult.ETag,
                        cacheResult.Stream);
                    ConsoleHelper.WriteLine(ConsoleColor.Blue, ConsoleColor.White, "Cache Not Modified: {0}", uri);
                    return cacheResult.Stream;
                }
            }
            throw ex;
        }

        private Stream HandleResponse(Uri uri, HttpWebResponse response)
        {
            var stream = new MemoryStream();
            using (var responseStream = response.GetResponseStream())
                responseStream.CopyTo(stream);
            stream.Rewind();
            _requestCache.Cache(uri, 
                response.GetLastModified(), 
                response.GetExpires(), 
                response.GetETag(),
                stream);
            return stream;
        }
    }
}